Skip to content

feat(engraving): add configurable rest position settings#2693

Open
roscopeeco wants to merge 2 commits into
CoderLine:developfrom
roscopeeco:feature/rest-position-setting
Open

feat(engraving): add configurable rest position settings#2693
roscopeeco wants to merge 2 commits into
CoderLine:developfrom
roscopeeco:feature/rest-position-setting

Conversation

@roscopeeco
Copy link
Copy Markdown

@roscopeeco roscopeeco commented May 5, 2026

Issues

#2572

Fixes #

Proposed changes

Adds restPositionMain and restPositionSecondary properties to EngravingSettings, allowing the vertical position of rests to be overridden per-voice via the RestPosition enum.

The RestPosition enum provides named staff-line positions (Line1-Line5 and the spaces between) calibrated to a 5-line staff. Positions are adjusted automatically for staves with fewer than 5 lines using the formula override - (5 - lineCount) * 2.

When either property is null (the default), rest positioning falls back to the existing auto-calculated behaviour so there is no change to existing scores.

These changes are purely to the typescript source code.

Checklist

  • [x ] I consent that this change becomes part of alphaTab under it's current or any future open source license
  • Changes are implemented
  • New tests were added
    I've tested it using creating a restPositionMain setting in the playground/demo/control app (this was not included in the pr). The test just dealt with a 5 line staff case. If you are happy with the content of the changes but need test i can investigate your test suite to see how they should be applied.

Further details

  • This is a breaking change
  • [ X] This change will require update of the documentation/website

Adds `restPositionMain` and `restPositionSecondary` properties to
`EngravingSettings`, allowing the vertical position of rests to be
overridden per-voice via the `RestPosition` enum.

The `RestPosition` enum provides named staff-line positions (Line1-Line5
and the spaces between) calibrated to a 5-line staff. Positions are
adjusted automatically for staves with fewer than 5 lines using the
formula `override - (5 - lineCount) * 2`.

When either property is null (the default), rest positioning falls
back to the existing auto-calculated behaviour so there is no change
to existing scores.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@roscopeeco
Copy link
Copy Markdown
Author

I'm thinking we could make this more granular and add per track settings for restPositionMain and restPositionSecondary.

This would be done on a per track basis in the score...something like track.settings.restPositionMain etc.

Adds RestPosition.test.ts with 21 visual tests covering:
- Default rendering (single voice and multi-voice) with no settings applied
- All RestPosition enum values for restPositionMain
- All rest durations with an override applied
- Multi-voice scenarios (both set, main only, secondary only)
- Staff line counts 1-5 to verify the line count adjustment formula

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@roscopeeco
Copy link
Copy Markdown
Author

Just added a set of tests. There is one pre-existing issue that the second test shows. The image rest-position-default-multi-voice.png highlights the issue.

The test doesn't apply any settings but in the image the secondary voice rest on bars 3 & 6 is above the staff

@Danielku15
Copy link
Copy Markdown
Member

I had a look at the code and the base already looks quite good. The biggest design contradiction currently is in where to configure rest positions. Looking at MusicXML the position of rests can be overridden for every rest individually via display-step and display-octave This is the approach I'd like to support in alphaTab.

  1. On Beat level we should hold the pitch where rests should be displayed
  2. During rest placement we compute the staff line on which the respective pitch is.

Having a "primary/secondary" displacement like you propose it, is still possible via a rewrite pass in the scoreLoaded event. But having it in the model allows also individual displacement.

Importer wise we should then support:

  • For MusicXML we should support the display- tags to fill the overrides
  • For alphaTex we should add a beat metadata tag to allow overriding

You can try to make an overall implementation if you like. Otherwise if you take care of the base (model + rendering) I can take over the importer support.

@roscopeeco
Copy link
Copy Markdown
Author

ok thanks for the response. I've got some questions for you:

  1. Are you okay with the RestPosition enums remaining in place?
  2. if so Is the importer going to map display step and display octave to a RestPosition enum value?
  3. Are you okay with naming the beat rest placement field restPosition?

i'm ok doing the model and rendering and can add the alphaTex beat metadata tags...i assume they are mimicking the musicXML naming? and then the imports handling are left to you?

@Danielku15
Copy link
Copy Markdown
Member

  1. No, they should rather be replaced with an octave+tone combination (aligned with the pitched note value). These values will then allow computing the staff line. The enum would be too limited to shift rests to any custom position.
  2. I'd say aligning with MusicXML and the current note naming: restDisplayTone: number = -1 restDisplayOctave: number = -1 as default.

For alphaTex it likely makes sense to just have one tag which accepts a pitch value like C5, and as name something like restDisplayPitch to express the combination of octave and tone. r.4 {restDisplayPitch C5}

@roscopeeco
Copy link
Copy Markdown
Author

Is there an existing staff property that indicates the starting octave for the staff? So the calculation would use this property as the baseline when using restDisplayTone and restDisplayOctave to determine the rendered rest position?

@Danielku15
Copy link
Copy Markdown
Member

  1. The AccidentalHelper has the logic to compute the right staff line for a given note
    public static computeStepsWithoutAccidentals(bar: Bar, note: Note) {
    let steps = 0;
    const noteValue = AccidentalHelper.getNoteValue(note);
    if (note.isPercussion) {
    steps = AccidentalHelper.getPercussionSteps(note);
    } else {
    const spelling = ModelUtils.resolveSpelling(bar.keySignature, noteValue, note.accidentalMode);
    steps = AccidentalHelper.calculateNoteSteps(bar.clef, spelling);
    }
    return steps;
    }
    In the same manner a helper for the rest can be added which gives you the "steps" (half staff line) for a given value.
  2. ScoreBarRenderer.getScoreY computes then the position for the given steps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants